#include "Pattern.h"
#include <iostream>
#include "Config.h"
#include "Seq.h"
#include "Log.h"
#include "Common.h"

using namespace std;

bool GetRefPixelArray( int x, int y, unsigned char * ref_pixel_array, unsigned char * frame, int width, int height );
unsigned char ComputeInterpolationPixel( unsigned char * ref_pixel_array, char * coff_array );

const int UD_OUTER_INDEX_MAP[14] = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 13, 12, 11 };
const int UD_BIT_SWAP_PAIR[][2] = { {0,10}, {1,9}, {2,8}, {3,7}, {4,6}, {11,13}, {14,18}, {15,17}, {-1,-1} };
const int UD_INNER_INDEX_MAP[6] = { 4, 3, 2, 1, 0, 5 };
const int LR_OUTER_INDEX_MAP[14] = { 3, 2, 1, 0, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4 };
const int LR_INNER_INDEX_MAP[6] = { 1, 0, 5, 4, 3, 2 };
const int LR_BIT_SWAP_PAIR[][2] = { {0,3}, {1,2}, {4,13}, {5,12}, {6,11}, {7,10}, {8,9}, {14,15}, {16,19}, {17,18}, {-1,-1} };



CPattern::CPattern( char * pattern_array, char * coff_array, unsigned int pattern_mask )
{

	m_uiPatternMask = pattern_mask;

	m_pCoffChanger = CCoffChanger::GetInstance( this );
	m_pCoffBucket = new CCoffBucket( MAX_BUCKET_SIZE );

	for ( int i=0; i<MIRROR_TYPE_COUNT; i++ )
	{
		m_vecpaPosList[i] = new vector<CPos>();
	}
		
	for ( int i=0; i<REF_PIXEL_NUMBER; i++ )
	{
		if ( GetPatternBit( m_uiPatternMask, i ) != 0 )
		{
			m_caCoff[MIRROR_NONE][i] = coff_array[i];
		}
		else
		{
			m_caCoff[MIRROR_NONE][i] = 0;
		}
	}

	m_uiaPattern[MIRROR_NONE] = PatternArrayToInt( pattern_array );
	m_uiaPattern[MIRROR_NONE] &= m_uiPatternMask;
	m_uiaPattern[MIRROR_NONE] = NormalizePatternForm( m_uiaPattern[MIRROR_NONE] );
	


	if ( !CConfig::GetConfig()->no_mirror )
	{
		DoUpDownMirror();
		DoLeftRightMirror();
		DoUpDownLeftRightMirror();

		DoSort();
	}

}

CPattern::~CPattern()
{
	for ( int i=0; i<MIRROR_TYPE_COUNT; i++ )
	{
		delete m_vecpaPosList[i];
	}
}

bool CPattern::DoMatch( char * pattern_array, CPos pos )
{
	unsigned int pattern = PatternArrayToInt( pattern_array );
	pattern &= m_uiPatternMask;
	pattern = NormalizePatternForm( pattern );

	bool match = false;
	for ( int i=0; i<MIRROR_TYPE_COUNT; i++ )
	{
		if ( pattern == m_uiaPattern[i] )
		{
			m_vecpaPosList[i]->push_back( pos );
			match = true;
			break;
		}

		if ( CConfig::GetConfig()->no_mirror )
		{
			break;
		}
	}

	if ( !match )
	{
		return false;
	}
	return true;
}


bool CPattern::DoUpDownMirror()
{
	unsigned int pattern = m_uiaPattern[MIRROR_NONE];
	char * src_pos;
	char * dest_pos;


	// Mirror pattern
	for ( int i=0; UD_BIT_SWAP_PAIR[i][0] != -1; i++ )
	{
		SwapBit( pattern, UD_BIT_SWAP_PAIR[i][0], UD_BIT_SWAP_PAIR[i][1] );
	}
	m_uiaPattern[MIRROR_UPDOWN] = NormalizePatternForm( pattern );

	// Mirror Coff
	src_pos = m_caCoff[MIRROR_NONE];
	dest_pos = m_caCoff[MIRROR_UPDOWN];
	for ( int i=0; i<14; i++ )
	{
		dest_pos[i] = src_pos[UD_OUTER_INDEX_MAP[i]];
	}

	src_pos = m_caCoff[MIRROR_NONE] + 14;
	dest_pos = m_caCoff[MIRROR_UPDOWN] + 14;
	for ( int i=0; i<6; i++ )
	{
		dest_pos[i] = src_pos[UD_INNER_INDEX_MAP[i]];
	}

	return true;
}

bool CPattern::DoLeftRightMirror()
{

	unsigned int pattern = m_uiaPattern[MIRROR_NONE];
	char * src_pos;
	char * dest_pos;

	// Mirror pattern
	for ( int i=0; LR_BIT_SWAP_PAIR[i][0] != -1; i++ )
	{
		SwapBit( pattern, LR_BIT_SWAP_PAIR[i][0], LR_BIT_SWAP_PAIR[i][1] );
	}
	m_uiaPattern[MIRROR_LEFTRIGHT] = NormalizePatternForm( pattern );

	// Mirror Coff
	src_pos = m_caCoff[MIRROR_NONE];
	dest_pos = m_caCoff[MIRROR_LEFTRIGHT];
	for ( int i=0; i<14; i++ )
	{
		dest_pos[i] = src_pos[LR_OUTER_INDEX_MAP[i]];
	}

	src_pos = m_caCoff[MIRROR_NONE] + 14;
	dest_pos = m_caCoff[MIRROR_LEFTRIGHT] + 14;
	for ( int i=0; i<6; i++ )
	{
		dest_pos[i] = src_pos[LR_INNER_INDEX_MAP[i]];
	}

	return true;
}

bool CPattern::DoUpDownLeftRightMirror()
{

	unsigned int pattern = m_uiaPattern[MIRROR_NONE];
	char pattern_array[REF_PIXEL_NUMBER];
	char mirror_pattern_array[REF_PIXEL_NUMBER];
	char * src_pos;
	char * dest_pos;

	// ========== Mirror pattern =========


	// UD Mirror begin
	for ( int i=0; UD_BIT_SWAP_PAIR[i][0] != -1; i++ )
	{
		SwapBit( pattern, UD_BIT_SWAP_PAIR[i][0], UD_BIT_SWAP_PAIR[i][1] );
	}

	// LR Mirror begin
	for ( int i=0; LR_BIT_SWAP_PAIR[i][0] != -1; i++ )
	{
		SwapBit( pattern, LR_BIT_SWAP_PAIR[i][0], LR_BIT_SWAP_PAIR[i][1] );
	}

	m_uiaPattern[MIRROR_UPDOWN_LEFTRIGHT] = NormalizePatternForm( pattern );


	// =========== Mirror Coff =========

	// UD Mirror begin
	src_pos = m_caCoff[MIRROR_NONE];
	dest_pos = mirror_pattern_array;
	for ( int i=0; i<14; i++ )
	{
		dest_pos[i] = src_pos[UD_OUTER_INDEX_MAP[i]];
	}

	src_pos = m_caCoff[MIRROR_NONE] + 14;
	dest_pos = mirror_pattern_array + 14;
	for ( int i=0; i<6; i++ )
	{
		dest_pos[i] = src_pos[UD_INNER_INDEX_MAP[i]];
	}

	// LR Mirror begin
	src_pos = mirror_pattern_array;
	dest_pos = m_caCoff[MIRROR_UPDOWN_LEFTRIGHT];
	for ( int i=0; i<14; i++ )
	{
		dest_pos[i] = src_pos[LR_OUTER_INDEX_MAP[i]];
	}

	src_pos = mirror_pattern_array + 14;
	dest_pos = m_caCoff[MIRROR_UPDOWN_LEFTRIGHT] + 14;
	for ( int i=0; i<6; i++ )
	{
		dest_pos[i] = src_pos[LR_INNER_INDEX_MAP[i]];
	}

	return true;
}


unsigned int CPattern::NormalizePatternForm( unsigned int pattern )
{
	if ( (pattern & NORMAL_TEST_BIT) == 0 )
	{
		return pattern;
	}
	else
	{
		return (~pattern) & PATTERN_VALID_BITMASK;
	}
}

void CPattern::Dump( bool dump_all )
{
	CLog * log = CLog::GetInstance();
	log->log_stream << "=== Dump Pattern: " << endl;

	char temp_pat[REF_PIXEL_NUMBER];

	if ( dump_all )
	{
		for ( int type=0; type<MIRROR_TYPE_COUNT; type++ )
		{
			log->log_stream << "TYPE " << type << ": " << hex << m_uiaPattern[type] << dec << "; ";

			PatternIntToArray( m_uiaPattern[type], temp_pat );
			for ( int i=0; i<REF_PIXEL_NUMBER; i++ )
			{
				log->log_stream << (int)temp_pat[i];
				if ( i != REF_PIXEL_NUMBER-1 )
				{
					log->log_stream << ",";
				}

			}
			log->log_stream << "; ";

			for ( int i=0; i<REF_PIXEL_NUMBER; i++ )
			{
				log->log_stream << (int)m_caCoff[type][i];
				if ( i != REF_PIXEL_NUMBER-1 )
				{
					log->log_stream << ",";
				}
			}
			log->log_stream << ";" << " COUNT: " << m_vecpaPosList[type]->size() << endl;


			if ( CConfig::GetConfig()->no_mirror )
			{
				break;
			}

		}

		log->log_stream << endl;
	}
	else
	{
		int count = 0;
		for ( int type=0; type<MIRROR_TYPE_COUNT; type++ )
		{
			count += m_vecpaPosList[type]->size();
			if ( CConfig::GetConfig()->no_mirror )
			{
				break;
			}
		}

		log->log_stream << "PATTERN: " << hex << m_uiaPattern[MIRROR_NONE] << dec << "; ";
		PatternIntToArray( m_uiaPattern[MIRROR_NONE], temp_pat );
		for ( int i=0; i<REF_PIXEL_NUMBER; i++ )
		{
			log->log_stream << (int)temp_pat[i];
			if ( i != REF_PIXEL_NUMBER-1 )
			{
				log->log_stream << ",";
			}

		}
		log->log_stream << "; COUNT: " << count << endl;

		log->log_stream << endl;
	}

}


bool CPattern::DoInterplate()
{

	CConfig * config = CConfig::GetConfig();

	vector<CPos>::iterator pos_iter;
	unsigned char ref_pixel_array[20];
	unsigned char inter_pixel = 0;
	unsigned char frame_no;
	int width = config->seq->m_iWidth;
	int height = config->seq->m_iHeight;
	int x;
	int y;
	vector<CPos> * pos_list = NULL;

	for ( int type = MIRROR_NONE; type < MIRROR_TYPE_COUNT; type++ )
	{
		pos_list = m_vecpaPosList[type];
		for ( pos_iter = pos_list->begin(); pos_iter!=pos_list->end(); pos_iter++ )
		{
			frame_no = (*pos_iter).m_ucFrame;
			x = (*pos_iter).m_usX;
			y = (*pos_iter).m_usY;

			GetRefPixelArray( x, y, ref_pixel_array, config->seq->m_ppRecPixelArray[frame_no], width, height );
			inter_pixel = ComputeInterpolationPixel( ref_pixel_array, m_caCoff[type] );
			config->seq->m_ppIntPixelArray[frame_no][y * width + x] = inter_pixel;
		}

		if ( CConfig::GetConfig()->no_mirror )
		{
			break;
		}
	}

	return true;
}

bool CPattern::ChangeCoff()
{
	// first time run this pattern
	if ( m_pCoffBucket->m_pvecCoffList->size() == 0 )
	{
		return true;
	}
	else
	{
		while( true ) 
		{
			m_pCoffChanger->Change( m_caCoff[MIRROR_NONE] );
			
			// Must no duplication
			if ( !m_pCoffBucket->FindDuplication( m_caCoff[MIRROR_NONE] ) )
			{
				int sum = 0;
				for ( int i=0; i<REF_PIXEL_NUMBER; i++ )
				{
					sum += m_caCoff[MIRROR_NONE][i];
				}

				// sum shoud not be 0
				if ( sum > 0 )
				{
					break;
				}
			}
		}
	}

	DoUpDownMirror();
	DoLeftRightMirror();
	DoUpDownLeftRightMirror();

	return true;
}

bool CPattern::RecordPSNR( double psnr )
{
	m_pCoffBucket->Insert( & CCoffItem( m_caCoff[MIRROR_NONE], psnr ) );
	return true;
}


void CPattern::DumpBucket()
{
	m_pCoffBucket->Dump();
}

bool CPattern::SelectBestCoff()
{

	if ( m_pCoffBucket->m_pvecCoffList->size() == 0 )
	{
		CLog::GetInstance()->log_stream << "ERROR: Empty bucket!" << endl;
		return false;
	}

	memcpy( m_caCoff[MIRROR_NONE], (*(m_pCoffBucket->m_pvecCoffList))[0]->coff, sizeof(char) * REF_PIXEL_NUMBER );
	DumpBestCoff();
	
	if ( ! CConfig::GetConfig()->no_mirror )
	{
		DoUpDownMirror();
		DoLeftRightMirror();
		DoUpDownLeftRightMirror();
	}

	return true;
}

void CPattern::DumpBestCoff()
{
	CLog * log = CLog::GetInstance();
	char temp_pat[REF_PIXEL_NUMBER];
	log->log_stream << "Best Coff For Pattern: ";

	PatternIntToArray( m_uiaPattern[MIRROR_NONE], temp_pat );
	for ( int i=0; i<REF_PIXEL_NUMBER; i++ )
	{
		log->log_stream << (int)temp_pat[i];
		if ( i != REF_PIXEL_NUMBER-1 )
		{
			log->log_stream << ",";
		}
	}
	log->log_stream << ";";

	if ( m_pCoffBucket->m_pvecCoffList->size() == 0 )
	{
		log->log_stream << " NONE" << endl;
	}
	else
	{
		for ( int i=0; i<REF_PIXEL_NUMBER; i++ )
		{
			log->log_stream << (int)(*(m_pCoffBucket->m_pvecCoffList))[0]->coff[i];
			if ( i != REF_PIXEL_NUMBER-1 )
			{
				log->log_stream << ",";
			}
		}
	}

	log->log_stream << endl;
}

bool CPattern::DoSort()
{
	//TODO optimize

	unsigned int min = 0x100000;
	int index = -1;
	for ( int i=0; i<MIRROR_TYPE_COUNT; i++ )
	{
		if ( m_uiaPattern[i] < min )
		{
			min = m_uiaPattern[i];
			index = i;
		}
	}
	
	if ( index != 0 )
	{
		// swap m_uiaPattern[0] & m_uiaPattern[index]

		m_uiaPattern[0] = m_uiaPattern[index];
		memcpy( m_caCoff[0], m_caCoff[index], sizeof(char) * REF_PIXEL_NUMBER );

		DoUpDownMirror();
		DoLeftRightMirror();
		DoUpDownLeftRightMirror();

	}

	return true;
}

void CPattern::SwapBit( unsigned int &pattern, int bit1_index, int bit2_index )
{
	bit1_index = REF_PIXEL_NUMBER-1-bit1_index;
	bit2_index = REF_PIXEL_NUMBER-1-bit2_index;

	unsigned int bit1 = pattern & (1<<bit1_index);
	unsigned int bit2 = pattern & (1<<bit2_index);

	if ( bit1 == 0 )
	{
		pattern = pattern & (~(1<<bit2_index));
	}
	else
	{
		pattern = pattern | (1<<bit2_index);
	}

	if ( bit2 == 0 )
	{
		pattern = pattern & (~(1<<bit1_index));
	}
	else
	{
		pattern = pattern | (1<<bit1_index);
	}

}